package info.niwota.fwrite;


import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;

/**
 * 
 * @author qiangli
 * 
 */
public class DocumentView extends ImageViewTouch {

	private static final String TAG = "DocumentView";

	public DocumentState state;
	
	// document lines and cursor
	private Bitmap cursor = null;

	private ArrayList<Line> lines = new ArrayList<Line>();
	
	private Line activeLine = null;

	//private Document fdoc;
	
	//
	private Context mContext;

	private final Paint mPaint = new Paint();

	private int penColor;

	private float penWidth;

	private int mScrollX;

	private int mScrollY;

	private boolean viewMode = true;

	private int linesize;

	private String backgroundUri;
	
	private DisplayMetrics mDisplayMetrics;

	public DocumentView(Context context) {
		super(context);
		init();
	}

	public DocumentView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public int addLine(Line line) {
		lines.add(line);
		return (lines.size() - 1);
	}

	@Override
	public void bringToFront() {
		// never
	}

	/**
	 * Clear lines and image
	 */
	@Override
	public void clear() {
		lines.clear();
		super.clear();
	}

	/**
	 * Clear lines but keep image
	 */
	public void clearLines() {
		Bitmap bm = getImageBitmap();
		lines.clear();
		super.setImageBitmapResetBase(bm);
	}

	public File capture() {
		File f = null;
		FileOutputStream fos = null;
		Context ctx = getContext();
		try {
			f = Utils.getOutputFile();
			if (f != null) {
				fos = new FileOutputStream(f);
			} else {
				// default
				final String name = "note.jpg";
				fos = ctx.openFileOutput(name, Context.MODE_WORLD_READABLE);
				f = ctx.getFileStreamPath(name);
			}
			if (fos != null) {
				writeDocument(fos);
			}
			//
			if (Debug.DEBUG) {
				Log.d(TAG, "saved: " + f);
			}
			return f;
		} catch (Throwable e) {
			if (f != null) {
				f.delete();
			}
			e.printStackTrace();
		} finally {
			//
			if (fos != null) {
				try {
					fos.close();
				} catch (Exception e) {
					//
				}
			}
		}
		return null;
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		return false;
	}

	private void drawCursor(Canvas canvas, float dx, float dy, int color, float width) {
		if (viewMode) {
			return;
		}
		
		float x = dx;
		float y = dy;
		if (cursor != null) {
			float ch = cursor.getHeight();
			float x1 = x + width / 2 + 1;
			float y1 = y;
			float y2 = y1 + ch;
		
			mPaint.setColor(color);
			mPaint.setStrokeWidth(width);
			//TODO compute and adjust the value
			final float off = 5;
			canvas.drawLine(x1, y1 + off, x1, y2 + off, mPaint);
			canvas.drawBitmap(cursor, x, y1, null);
		}
	}

	private void drawDocument(Canvas canvas) {
		final int lineCount = lines.size();

		if (Debug.DEBUG) {
			Log.d(TAG, "drawDocument lineCount: " + lineCount);
		}

		int width = getWidth();
		int height = getHeight();
		//
		float dx = getPaddingLeft();
		float dy = getPaddingTop();
		//
		if (lineCount <= 0) {
			drawCursor(canvas, dx, dy, penColor, penWidth);
			return;
		}

		float mh = measuredHeight(width, dx, dy);

		//docHeight = mh;
		//
		if (Debug.DEBUG) {
			Log.d(TAG, "drawDocument " + canvas + " count: " + lineCount
					+ " width: " + width + " height: " + height + " mh: " + mh);
		}

		mh = mh * 10 / 7; // scale so always show some extra blank lines
		float mr = (mh > height ? height / mh : 1);

		float scale = getScale();

		int saveCount = canvas.save();

		float sx = scale * mr;
		float sy = sx;
		//

		canvas.scale(sx, sy);

		drawDocument(canvas, lineCount, width, height, dx, dy, true);
		//
		canvas.restoreToCount(saveCount);
	}

	private void drawDocument(final Canvas canvas, final int lineCount,
			final int width, final int height, final float dx, final float dy,
			final boolean drawCursor) {
		float x = dx;
		float y = dy;
		//
		for (int i = 0; i < lineCount; i += 1) {	
			final Line line = lines.get(i);
			final int elCount = line.count();
			final float lh = line.getHeight();
			
			for (int j = 0; j < elCount; j += 1) {
				Element el = line.get(j);
				// auto wrap
				if (x + el.width > width) {
					x = dx;
					y += lh;
				}
				
				el.setContext(mContext);
				el.draw(canvas, mPaint, x, y, height, lh);
				
				x += el.width;
			}

			// draw cursor
			if (drawCursor && activeLine == null && i == (lineCount - 1)) {
				drawCursor(canvas, x, y, penColor, penWidth);
			} else if (drawCursor && line == activeLine) {
				boolean peer = line.getPeer() != null;
				int pcolor = peer ? Color.BLACK : penColor;
				float pwidth = peer ? 0.0f : penWidth;
				drawCursor(canvas, x, y, pcolor, pwidth);
			}
			//
			x = dx;
			y += lh;
			
			//draw a line
			//mPaint.setStrokeWidth(1);
			//canvas.drawLine(x, y, width, y, mPaint);
		}
		//
		if (Debug.DEBUG) {
			Log.d(TAG, "x: " + x + " y: " + y);
		}
	}

	public Line getLine(int line) {
		return lines.get(line);
	}

	@Override
	protected int getSuggestedMinimumHeight() {
		int s = super.getSuggestedMinimumHeight();
		if (Debug.DEBUG) {
			Log.d(TAG, "getSuggestedMinimumHeight " + s);
		}
		return s;
	}

	@Override
	protected int getSuggestedMinimumWidth() {
		int s = super.getSuggestedMinimumWidth();
		if (Debug.DEBUG) {
			Log.d(TAG, "getSuggestedMinimumWidth " + s);
		}
		return s;
	}

	private void init() {
		cursor = BitmapFactory.decodeResource(getResources(),
				R.drawable.pen_brush_22);
		super.setDrawingCacheEnabled(true);
		
		mContext = getContext();
		
		mPaint.setTypeface(Typeface.MONOSPACE);
		mPaint.setTextAlign(Align.LEFT);
		mPaint.setAntiAlias(true);
		
		mDisplayMetrics = mContext.getResources().getDisplayMetrics();
		
		if (Debug.DEBUG) {
			Log.d(TAG, "density: " + mDisplayMetrics.density);
		}
	}

	@Override
	public void invalidate() {
		if (Debug.DEBUG) {
			Log.d(TAG, "invalidate");
		}
		
		super.invalidate();
	}


	@Override
	public boolean isDrawingCacheEnabled() {
		if (Debug.DEBUG) {
			Log.d(TAG, "isDrawingCacheEnabled");
		}
		return true;
	}

	public boolean isEmpty() {
		return (lines == null || lines.size() == 0);
	}

	@Override
	public boolean isFocused() {
		if (Debug.DEBUG) {
			Log.d(TAG, "isFocused");
		}
		return false;
	}

	@Override
	public boolean isInEditMode() {
		boolean b = super.isInEditMode();
		if (Debug.DEBUG) {
			Log.d(TAG, "isInEditMode " + b);
		}
		return b;
	}

	@Override
	public boolean isInTouchMode() {
		boolean b = super.isInTouchMode();
		if (Debug.DEBUG) {
			Log.d(TAG, "isInTouchMode " + b);
		}
		return b;
	}

	@Override
	public boolean isLongClickable() {
		boolean b = super.isLongClickable();
		if (Debug.DEBUG) {
			Log.d(TAG, "isLongClickable " + b);
		}
		return b;
	}

	@Override
	public boolean isSelected() {
		if (Debug.DEBUG) {
			Log.d(TAG, "isSelected");
		}
		return false;
	}
	
	public Line lastLine(String peer) {
		int len = lines.size() - 1;
		if (len >= 0) {
			for (int i = len; i >=0; i--) {
				Line line = lines.get(i);
				String p = line.getPeer();
				if (peer == null && p == null || peer != null && p != null && p.equals(peer)) {
					return line;
				}
			}
		}
		return null;
	}

	private float measuredHeight(final int width, float dx, float dy) {
		final int lineCount = (lines == null ? 0 : lines.size());
		float x = dx;
		float y = dy;
	
		for (int i = 0; i < lineCount; i += 1) {
			Line line = lines.get(i);
			int elCount = line.count();
			float lh = line.getHeight();
			for (int j = 0; j < elCount; j += 1) {
				Element el = line.get(j);
				//
				el.recompute(mPaint);
				
				// auto wrap
				if (x + el.width > width) {
					x = dx;
					y += lh;
				}
				x += el.width;
			}

			if (i == (lineCount - 1)) {
				break;
			}
			x = dx;
			y += lh;
		}
		return y;
	}

	@Override
	public boolean onCheckIsTextEditor() {
		boolean b = super.onCheckIsTextEditor();
		if (Debug.DEBUG) {
			Log.d(TAG, "onCheckIsTextEditor");
		}
		return b;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (Debug.DEBUG) {
			Log.d(TAG, "onDraw");
		}
		super.onDraw(canvas);
		drawDocument(canvas);
	}

	@Override
	protected void onFinishInflate() {
		super.onFinishInflate();
		if (Debug.DEBUG) {
			Log.d(TAG, "onFinishInflate: ");
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		if (Debug.DEBUG) {
			Log.d(TAG, "onLayout " + changed + " left: " + left + " top: "
					+ top + " right: " + right + " bottom: " + bottom);
		}
		super.onLayout(changed, left, top, right, bottom);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		if (Debug.DEBUG) {
			Log.d(TAG, "onMeasure " + widthMeasureSpec + ":"
					+ heightMeasureSpec);
		}
		// super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		setMeasuredDimension(measureWidth(widthMeasureSpec),
				measureHeight(heightMeasureSpec));
	}

	private int measureWidth(int measureSpec) {
        int w = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            w = specSize;
        } else {
            w = getSuggestedMinimumWidth() + getPaddingLeft()
                    + getPaddingRight();
            if (specMode == MeasureSpec.AT_MOST) {
                w = Math.min(w, specSize);
            }
        }

        return w;
    }

    private int measureHeight(int measureSpec) {
        int h = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            h = specSize;
        } else {
        	DisplayMetrics dm = this.getContext().getResources().getDisplayMetrics();
        	final int maxWidth = (int)(dm.widthPixels / dm.density);
        	
            h = (int) measuredHeight(maxWidth, 0.0f, 0.0f) + getPaddingTop()
                    + getPaddingBottom();

            h = Math.max(h, getSuggestedMinimumHeight());
            if (specMode == MeasureSpec.AT_MOST) {
                h = Math.min(h, specSize);
            }
        }
        return h;
    }
    
	@Override
	protected void onRestoreInstanceState(Parcelable state) {
		super.onRestoreInstanceState(state);
		if (Debug.DEBUG) {
			Log.d(TAG, "onRestoreInstanceState: " + state);
		}
	}

	@Override
	protected Parcelable onSaveInstanceState() {
		if (Debug.DEBUG) {
			Log.d(TAG, "onSaveInstanceState");
		}
		Parcelable out = super.onSaveInstanceState();
		//
		return out;
	}

	@Override
	protected void onScrollChanged(int l, int t, int oldl, int oldt) {
		if (Debug.DEBUG) {
			Log.d(TAG, "onScrollChanged " + l + ":" + t + " old " + oldl + ":"
					+ oldt);
		}
		super.onScrollChanged(l, t, oldl, oldt);
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		if (Debug.DEBUG) {
			Log.d(TAG, "onSizeChanged " + w + ":" + h + " old " + oldw + ":"
					+ oldh);
		}
		super.onSizeChanged(w, h, oldw, oldh);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}
	
	public Line removeLastLine(String peer) {
		int len = lines.size() - 1;
		if (len >= 0) {
			for (int i = len; i >=0; i--) {
				Line line = lines.get(i);
				String p = line.getPeer();
				if (peer == null && p == null || peer != null && p != null && p.equals(peer)) {
					return lines.remove(i);
				}
			}
		}
		return null;
	}

	public void restoreState(Bundle savedInstanceState) {
		lines = savedInstanceState.getParcelableArrayList("lines");
	}

	public void saveState(Bundle outState) {
		outState.putParcelableArrayList("lines", lines);
	}

	@Override
	public void scrollBy(int x, int y) {
		if (Debug.DEBUG) {
			Log.d(TAG, "scrollBy " + x + ":" + y);
		}
		// int sx = x;
		// int sy = y;
		// super.scrollBy(sx, sy);

		scrollTo(mScrollX + x, mScrollY + y);
	}

	@Override
	public void scrollTo(int x, int y) {
		if (Debug.DEBUG) {
			Log.d(TAG, "scrollTo " + x + ":" + y);
		}
		float scale = getScale();
		int maxScrollX = (int) (getWidth() * scale);
		int maxScrollY = (int) (getHeight() * scale );
		//
		x = Math.max(0, Math.min(maxScrollX, x));
		y = Math.max(0, Math.min(maxScrollY, y));
		if (Debug.DEBUG) {
			Log.d(TAG, "adjusted scrollTo " + x + ":" + y);
		}
		
		mScrollX = x;
		mScrollY = y;
		
		super.scrollTo(x, y);
	}

	@Override
	protected boolean setFrame(int l, int t, int r, int b) {
		if (Debug.DEBUG) {
			Log.d(TAG, "setFrame " + l + ":" + t + ":" + r + ":" + b);
		}
		return super.setFrame(l, t, r, b);
	}

	public void setPenColor(int penColor) {
		this.penColor = penColor;
	}

	public void setPenWidth(float penWidth) {
		this.penWidth = penWidth;
	}

	@Override
	public boolean willNotCacheDrawing() {
		if (Debug.DEBUG) {
			Log.d(TAG, "willNotCacheDrawing");
		}
		return false;
	}

	@Override
	public boolean willNotDraw() {
		if (Debug.DEBUG) {
			Log.d(TAG, "willNotDraw");
		}
		return false;
	}

	private void writeDocument(OutputStream fos) {
		int lineCount = lines.size();
		int width = getWidth();
		int height = getHeight();
		//
		float dx = 0;
		float dy = 0;

		float mh = measuredHeight(width, dx, dy);
		//
		Bitmap.Config cf = Bitmap.Config.ARGB_8888;
		// Bitmap b = Bitmap.createBitmap(getWidth(), getHeight(), cf);
		Bitmap b = Bitmap.createBitmap(getWidth(), (int) mh + 64, cf);
		Canvas c = new Canvas(b);
		drawDocument(c, lineCount, width, height, dx, dy, false);
		//
		// /TODO settings
		b.compress(Bitmap.CompressFormat.JPEG, 100, fos);
	}

	public boolean isViewMode() {
		return viewMode;
	}

	/**
	 * 
	 * @param view mode true|false
	 */
	public void setViewMode(boolean view) {
		viewMode = view;
	}

	public ArrayList<Line> getLines() {
		return lines;
	}

	public void setActiveLine(Line activeLine) {
		this.activeLine = activeLine;
	}
	
	public int getLinesize() {
		return linesize;
	}

	public void setLinesize(int size) {
		this.linesize = size;
	}
	
	public Paint getPaint() {
		return mPaint;
	}

	public void setColor(int color) {
		mPaint.setColor(color);
	}
	
	public void setTextSize(float size) {
		mPaint.setTextSize(size);
	}

	public String getBackgroundUri() {
		return backgroundUri;
	}

	public void setBackgroundUri(Uri uri) {
		this.backgroundUri = uri.toString();
		
		Drawable d = Utils.loadImage(getContext(), uri);
		super.setBackgroundDrawable(d);
	}
}